import { arrayFill } from '../../Source/Cesium.js';
import { Cartesian3 } from '../../Source/Cesium.js';
import { EllipsoidGeometry } from '../../Source/Cesium.js';
import { GeometryOffsetAttribute } from '../../Source/Cesium.js';
import { Math as CesiumMath } from '../../Source/Cesium.js';
import { VertexFormat } from '../../Source/Cesium.js';
import createPackableSpecs from '../createPackableSpecs.js';

describe('Core/EllipsoidGeometry', function() {
    it('constructor rounds floating-point slicePartitions', function() {
        var m = new EllipsoidGeometry({
            slicePartitions : 3.5,
            stackPartitions : 3
        });
        expect(m._slicePartitions).toEqual(4);
    });

    it('constructor rounds floating-point stackPartitions', function() {
        var m = new EllipsoidGeometry({
            slicePartitions : 3,
            stackPartitions : 3.5
        });
        expect(m._stackPartitions).toEqual(4);
    });

    it('constructor throws with invalid slicePartitions', function() {
        expect(function() {
            return new EllipsoidGeometry({
                slicePartitions : -1
            });
        }).toThrowDeveloperError();
    });

    it('constructor throws with invalid stackPartitions', function() {
        expect(function() {
            return new EllipsoidGeometry({
                stackPartitions : -1
            });
        }).toThrowDeveloperError();
    });

    it('computes positions', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions : 3,
            stackPartitions : 3
        }));

        // The vertices are 6x6 because an additional slice and stack are added
        // and the first and last clock and cone angles are duplicated (3 + 1 + 2 = 6)
        var numVertices = 36; // 6 rows * 6 positions
        var numTriangles = 18; // 6 top + 6 bottom + 6 around the sides
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes offset attribute', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions : 3,
            stackPartitions : 3,
            offsetAttribute : GeometryOffsetAttribute.ALL
        }));

        var numVertices = 36;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);

        var offset = m.attributes.applyOffset.values;
        expect(offset.length).toEqual(numVertices);
        var expected = new Array(offset.length);
        expected = arrayFill(expected, 1);
        expect(offset).toEqual(expected);
    });

    it('compute all vertex attributes', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            slicePartitions : 3,
            stackPartitions : 3
        }));

        var numVertices = 36;
        var numTriangles = 18;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.attributes.st.values.length).toEqual(numVertices * 2);
        expect(m.attributes.normal.values.length).toEqual(numVertices * 3);
        expect(m.attributes.tangent.values.length).toEqual(numVertices * 3);
        expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
    });

    it('computes attributes for a unit sphere', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            slicePartitions : 3,
            stackPartitions : 3
        }));

        var positions = m.attributes.position.values;
        var normals = m.attributes.normal.values;
        var tangents = m.attributes.tangent.values;
        var bitangents = m.attributes.bitangent.values;

        for (var i = 0; i < positions.length; i += 3) {
            var position = Cartesian3.fromArray(positions, i);
            var normal = Cartesian3.fromArray(normals, i);
            var tangent = Cartesian3.fromArray(tangents, i);
            var bitangent = Cartesian3.fromArray(bitangents, i);

            expect(Cartesian3.magnitude(position)).toEqualEpsilon(1.0, CesiumMath.EPSILON10);
            expect(normal).toEqualEpsilon(Cartesian3.normalize(position, new Cartesian3()), CesiumMath.EPSILON7);
            expect(Cartesian3.dot(Cartesian3.UNIT_Z, tangent)).not.toBeLessThan(0.0);
            expect(bitangent).toEqualEpsilon(Cartesian3.cross(normal, tangent, new Cartesian3()), CesiumMath.EPSILON7);
        }
    });

    it('computes positions with inner surface', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions : 3,
            stackPartitions : 3,
            innerRadii : new Cartesian3(0.5, 0.5, 0.5)
        }));

        var numVertices = 72; // 6 rows * 6 positions * 2 surfaces
        var numTriangles = 36; // (6 top + 6 bottom + 6 around the sides) * 2 surfaces
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes positions with inner surface and partial clock range', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions : 4,
            stackPartitions : 4,
            innerRadii : new Cartesian3(0.5, 0.5, 0.5),
            minimumClock : CesiumMath.toRadians(90.0),
            maximumClock : CesiumMath.toRadians(270.0)
        }));

        var numVertices = 70;
        var numTriangles = 48;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes positions with inner surface and partial clock range and open top', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions : 4,
            stackPartitions : 4,
            innerRadii : new Cartesian3(0.5, 0.5, 0.5),
            minimumClock : CesiumMath.toRadians(90.0),
            maximumClock : CesiumMath.toRadians(270.0),
            minimumCone : CesiumMath.toRadians(30.0)
        }));

        var numVertices = 60;
        var numTriangles = 40;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes partitions to default to 2 if less than 2', function() {
        var geometry = new EllipsoidGeometry({
            radii : new Cartesian3(0.5, 0.5, 0.5)
        });

        geometry._slicePartitions = 0;
        geometry._stackPartitions = 0;

        var m = EllipsoidGeometry.createGeometry(geometry);

        expect(m.indices.length).toEqual(6);
    });

    it('negates normals on an ellipsoid', function() {
        var negatedNormals = 0;

        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            radii : new Cartesian3(1.0, 1.0, 1.0),
            innerRadii : new Cartesian3(0.5, 0.5, 0.5),
            minimumCone : CesiumMath.toRadians(60.0),
            maximumCone : CesiumMath.toRadians(140.0)
        }));

        var positions = m.attributes.position.values;
        var normals = m.attributes.normal.values;

        for (var i = 0; i < positions.length; i += 3) {
            var normal = Cartesian3.fromArray(normals, i);

            if (normal.x < 0 && normal.y < 0 && normal.z < 0) {
                negatedNormals++;
            }
        }

        expect(negatedNormals).toEqual(496);
    });

    it('computes the unit ellipsoid', function() {
        var ellipsoid = EllipsoidGeometry.getUnitEllipsoid();
        expect(ellipsoid).toBeDefined();
        expect(ellipsoid.boundingSphere.radius).toEqual(1);

        expect(EllipsoidGeometry.getUnitEllipsoid()).toBe(ellipsoid);
    });

    it('computes positions with inner surface and partial clock range and open top and bottom', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions : 4,
            stackPartitions : 4,
            innerRadii : new Cartesian3(0.5, 0.5, 0.5),
            minimumClock : CesiumMath.toRadians(90.0),
            maximumClock : CesiumMath.toRadians(270.0),
            minimumCone : CesiumMath.toRadians(30.0),
            maximumCone : CesiumMath.toRadians(120.0)
        }));

        var numVertices = 50;
        var numTriangles = 32;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('undefined is returned if the x, y, or z radii or innerRadii are equal or less than zero', function() {
        var ellipsoid0 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(0.0, 500000.0, 500000.0)
        });
        var ellipsoid1 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 0.0, 500000.0)
        });
        var ellipsoid2 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 500000.0, 0.0)
        });
        var ellipsoid3 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(-10.0, 500000.0, 500000.0)
        });
        var ellipsoid4 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, -10.0, 500000.0)
        });
        var ellipsoid5 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 500000.0, -10.0)
        });
        var ellipsoid6 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(0.0, 100000.0, 100000.0)
        });
        var ellipsoid7 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, 0.0, 100000.0)
        });
        var ellipsoid8 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, 100000.0, 0.0)
        });
        var ellipsoid9 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(-10.0, 100000.0, 100000.0)
        });
        var ellipsoid10 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, -10.0, 100000.0)
        });
        var ellipsoid11 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, 100000.0, -10.0)
        });

        var geometry0 = EllipsoidGeometry.createGeometry(ellipsoid0);
        var geometry1 = EllipsoidGeometry.createGeometry(ellipsoid1);
        var geometry2 = EllipsoidGeometry.createGeometry(ellipsoid2);
        var geometry3 = EllipsoidGeometry.createGeometry(ellipsoid3);
        var geometry4 = EllipsoidGeometry.createGeometry(ellipsoid4);
        var geometry5 = EllipsoidGeometry.createGeometry(ellipsoid5);
        var geometry6 = EllipsoidGeometry.createGeometry(ellipsoid6);
        var geometry7 = EllipsoidGeometry.createGeometry(ellipsoid7);
        var geometry8 = EllipsoidGeometry.createGeometry(ellipsoid8);
        var geometry9 = EllipsoidGeometry.createGeometry(ellipsoid9);
        var geometry10 = EllipsoidGeometry.createGeometry(ellipsoid10);
        var geometry11 = EllipsoidGeometry.createGeometry(ellipsoid11);

        expect(geometry0).toBeUndefined();
        expect(geometry1).toBeUndefined();
        expect(geometry2).toBeUndefined();
        expect(geometry3).toBeUndefined();
        expect(geometry4).toBeUndefined();
        expect(geometry5).toBeUndefined();
        expect(geometry6).toBeUndefined();
        expect(geometry7).toBeUndefined();
        expect(geometry8).toBeUndefined();
        expect(geometry9).toBeUndefined();
        expect(geometry10).toBeUndefined();
        expect(geometry11).toBeUndefined();
    });

    var ellipsoidgeometry = new EllipsoidGeometry({
        vertexFormat : VertexFormat.POSITION_ONLY,
        radii : new Cartesian3(1.0, 2.0, 3.0),
        innerRadii : new Cartesian3(0.5, 0.6, 0.7),
        minimumClock : 0.1,
        maximumClock : 0.2,
        minimumCone : 0.3,
        maximumCone : 0.4,
        slicePartitions : 3,
        stackPartitions : 3
    });
    var packedInstance = [
        1.0, 2.0, 3.0,
        0.5, 0.6, 0.7,
        1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
        0.1, 0.2,
        0.3, 0.4,
        3.0, 3.0,
        -1
    ];
    createPackableSpecs(EllipsoidGeometry, ellipsoidgeometry, packedInstance);
});
